Tìm hiểu cách triển khai ErrorBoundary trong React để xử lý lỗi một cách mượt mà, cải thiện trải nghiệm người dùng và ngăn chặn sự cố ứng dụng. Hướng dẫn này bao gồm cô lập lỗi, các phương pháp hay nhất và kỹ thuật nâng cao.
React ErrorBoundary: Hướng Dẫn Toàn Diện về Cô Lập Lỗi
Trong thế giới phát triển web năng động, việc xây dựng các ứng dụng mạnh mẽ và có khả năng phục hồi là tối quan trọng. React, một thư viện JavaScript phổ biến để xây dựng giao diện người dùng, cung cấp một cơ chế mạnh mẽ để xử lý lỗi một cách mượt mà: ErrorBoundary. Hướng dẫn này đi sâu vào sự phức tạp của React ErrorBoundary, khám phá mục đích, cách triển khai, các phương pháp hay nhất và các kỹ thuật nâng cao để đảm bảo trải nghiệm người dùng mượt mà ngay cả khi đối mặt với các lỗi không mong muốn.
ErrorBoundary là gì?
ErrorBoundary là một thành phần React bắt các lỗi JavaScript ở bất kỳ đâu trong cây thành phần con của nó, ghi lại các lỗi đó và hiển thị một giao diện người dùng dự phòng thay vì làm sập toàn bộ ứng dụng. Hãy coi nó như một mạng lưới an toàn ngăn chặn sự thất bại của một thành phần duy nhất lan rộng và làm gián đoạn toàn bộ trải nghiệm người dùng.
Trước khi ErrorBoundary được giới thiệu, các lỗi JavaScript không được xử lý trong các thành phần React có thể dẫn đến việc gỡ bỏ toàn bộ cây thành phần, dẫn đến màn hình trắng hoặc ứng dụng bị hỏng. ErrorBoundary cung cấp một cách để ngăn chặn thiệt hại và cung cấp khả năng phục hồi mượt mà hơn.
Tại sao nên sử dụng ErrorBoundary?
- Cải thiện trải nghiệm người dùng: Thay vì bị sập đột ngột, người dùng sẽ thấy một thông báo dự phòng hữu ích, duy trì nhận thức tích cực về ứng dụng của bạn.
- Cô lập lỗi: ErrorBoundary cô lập lỗi vào các phần cụ thể của ứng dụng, ngăn chúng ảnh hưởng đến các khu vực không liên quan khác.
- Hỗ trợ gỡ lỗi: Bằng cách ghi lại lỗi, ErrorBoundary cung cấp thông tin chi tiết có giá trị về nguyên nhân gốc rễ của sự cố, tạo điều kiện thuận lợi cho việc gỡ lỗi và bảo trì.
- Ổn định ứng dụng: ErrorBoundary nâng cao sự ổn định và khả năng phục hồi tổng thể của ứng dụng, làm cho nó đáng tin cậy hơn đối với người dùng.
Tạo một Thành phần ErrorBoundary
Việc tạo một thành phần ErrorBoundary trong React tương đối đơn giản. Nó bao gồm việc định nghĩa một class component (ErrorBoundary phải là class component) với các phương thức vòng đời static getDerivedStateFromError() và componentDidCatch().
Ví dụ cơ bản
Đây là một ví dụ cơ bản về thành phần ErrorBoundary:
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = {
hasError: false
};
}
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return {
hasError: true
};
}
componentDidCatch(error, errorInfo) {
// You can also log the error to an error reporting service
console.error(error, errorInfo);
// logErrorToMyService(error, errorInfo);
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return (
Something went wrong.
);
}
return this.props.children;
}
}
export default ErrorBoundary;
Giải thích:
constructor(props): Khởi tạo trạng thái của thành phần vớihasErrorđược đặt thànhfalse.static getDerivedStateFromError(error): Phương thức tĩnh này được gọi sau khi một lỗi đã được ném ra bởi một thành phần con. Nó nhận lỗi đã được ném ra làm đối số và nên trả về một giá trị để cập nhật trạng thái. Trong trường hợp này, nó đặthasErrorthànhtrue, kích hoạt giao diện người dùng dự phòng.componentDidCatch(error, errorInfo): Phương thức này được gọi sau khi một lỗi đã được ném ra bởi một thành phần con. Nó nhận lỗi và một đối tượng chứa thông tin về thành phần nào đã ném ra lỗi. Đây là nơi lý tưởng để ghi lại lỗi vào một dịch vụ báo cáo lỗi hoặc thực hiện các tác dụng phụ khác. Đối tượngerrorInfochứa một khóacomponentStackvới thông tin về thành phần đã ném ra lỗi.render(): Phương thức này hiển thị đầu ra của thành phần. NếuhasErrorlàtrue, nó sẽ hiển thị một giao diện người dùng dự phòng (trong trường hợp này là một thông báo đơn giản "Something went wrong."). Nếu không, nó sẽ hiển thị các thành phần con của nó (this.props.children).
Sử dụng Thành phần ErrorBoundary
Để sử dụng ErrorBoundary, chỉ cần bao bọc bất kỳ thành phần hoặc phần nào của ứng dụng mà bạn muốn bảo vệ bằng thành phần ErrorBoundary:
import ErrorBoundary from './ErrorBoundary';
function MyComponent() {
return (
);
}
export default MyComponent;
Nếu MyPotentiallyErrorProneComponent ném ra lỗi, ErrorBoundary sẽ bắt nó, ghi lại và hiển thị giao diện người dùng dự phòng.
Các Phương pháp Tốt nhất để Triển khai ErrorBoundary
Để tối đa hóa hiệu quả của ErrorBoundary, hãy xem xét các phương pháp tốt nhất sau:
- Vị trí chiến lược: Đặt ErrorBoundary một cách chiến lược xung quanh các thành phần có khả năng cao ném ra lỗi hoặc những thành phần quan trọng đối với trải nghiệm người dùng. Đừng bao bọc toàn bộ ứng dụng của bạn trong một ErrorBoundary duy nhất. Thay vào đó, hãy sử dụng nhiều ErrorBoundary để cô lập các lỗi vào các khu vực cụ thể.
- Xử lý lỗi chi tiết: Hướng tới việc xử lý lỗi chi tiết bằng cách đặt ErrorBoundary gần hơn với các thành phần có thể gặp sự cố. Điều này cho phép bạn cung cấp các giao diện người dùng dự phòng cụ thể hơn và ngăn chặn sự gián đoạn không cần thiết cho các phần khác của ứng dụng.
- Giao diện người dùng dự phòng cung cấp thông tin: Cung cấp một giao diện người dùng dự phòng rõ ràng và hữu ích, thông báo cho người dùng về lỗi và đề xuất các giải pháp khả thi. Tránh các thông báo lỗi chung chung. Thay vào đó, hãy cung cấp ngữ cảnh và hướng dẫn. Ví dụ, nếu lỗi là do sự cố mạng, hãy đề nghị kiểm tra kết nối internet.
- Ghi lại lỗi: Ghi lại lỗi bằng
componentDidCatch()vào một dịch vụ báo cáo lỗi (ví dụ: Sentry, Rollbar) hoặc nhật ký phía máy chủ của bạn. Điều này cho phép bạn theo dõi và giải quyết lỗi một cách chủ động. Bao gồm ngữ cảnh liên quan trong nhật ký, chẳng hạn như component stack và thông tin người dùng. - Cơ chế thử lại: Cân nhắc triển khai cơ chế thử lại trong giao diện người dùng dự phòng của bạn. Ví dụ, cung cấp một nút cho phép người dùng thử lại thao tác đã thất bại. Điều này có thể đặc biệt hữu ích để xử lý các lỗi tạm thời, chẳng hạn như sự cố mạng.
- Tránh hiển thị trực tiếp ErrorBoundary: ErrorBoundary được thiết kế để bắt lỗi trong các thành phần con của chúng. Việc hiển thị trực tiếp một ErrorBoundary bên trong chính nó sẽ không bắt được các lỗi được ném ra trong quá trình hiển thị của chính nó.
- Không sử dụng ErrorBoundary cho các lỗi có thể lường trước: ErrorBoundary dành cho các lỗi không mong muốn. Đối với các lỗi có thể lường trước, chẳng hạn như lỗi xác thực hoặc lỗi API, hãy sử dụng khối try/catch hoặc các cơ chế xử lý lỗi khác trong chính thành phần đó.
Các Kỹ thuật ErrorBoundary Nâng cao
Ngoài việc triển khai cơ bản, có một số kỹ thuật nâng cao bạn có thể sử dụng để cải thiện việc triển khai ErrorBoundary của mình:
Báo cáo lỗi tùy chỉnh
Thay vì chỉ đơn giản ghi lại lỗi vào console, bạn có thể tích hợp ErrorBoundary với một dịch vụ báo cáo lỗi chuyên dụng. Các dịch vụ như Sentry, Rollbar và Bugsnag cung cấp các công cụ để theo dõi, phân tích và giải quyết lỗi trong ứng dụng của bạn. Để tích hợp với một dịch vụ như vậy, bạn thường sẽ cài đặt SDK của dịch vụ và sau đó gọi hàm báo cáo lỗi của nó trong phương thức componentDidCatch():
componentDidCatch(error, errorInfo) {
// Log the error to Sentry
Sentry.captureException(error, { extra: errorInfo });
}
Giao diện người dùng dự phòng động
Thay vì hiển thị một giao diện người dùng dự phòng tĩnh, bạn có thể tạo giao diện người dùng dự phòng một cách động dựa trên loại lỗi đã xảy ra. Điều này cho phép bạn cung cấp các thông báo cụ thể và hữu ích hơn cho người dùng. Ví dụ, bạn có thể hiển thị một thông báo khác cho lỗi mạng, lỗi xác thực hoặc lỗi xác thực dữ liệu.
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = {
hasError: false,
errorType: null
};
}
static getDerivedStateFromError(error) {
let errorType = 'generic';
if (error instanceof NetworkError) {
errorType = 'network';
} else if (error instanceof AuthenticationError) {
errorType = 'authentication';
}
// Update state so the next render will show the fallback UI.
return {
hasError: true,
errorType: errorType
};
}
render() {
if (this.state.hasError) {
switch (this.state.errorType) {
case 'network':
return (Network error. Please check your connection.
);
case 'authentication':
return (Authentication error. Please log in again.
);
default:
return (Something went wrong.
);
}
}
return this.props.children;
}
}
Sử dụng ErrorBoundary với Server-Side Rendering (SSR)
Khi sử dụng Server-Side Rendering (SSR), ErrorBoundary có thể phức tạp vì các lỗi xảy ra trong lần hiển thị ban đầu trên máy chủ có thể khiến toàn bộ quá trình render phía máy chủ thất bại. Để xử lý điều này, bạn có thể sử dụng kết hợp các khối try/catch và ErrorBoundary. Bao bọc quá trình render trong một khối try/catch và sau đó hiển thị giao diện người dùng dự phòng của ErrorBoundary nếu có lỗi xảy ra. Điều này sẽ ngăn máy chủ bị sập và cho phép bạn phục vụ một trang HTML cơ bản với một thông báo lỗi.
ErrorBoundary và các thư viện của bên thứ ba
Khi tích hợp các thư viện của bên thứ ba vào ứng dụng React của bạn, điều cần thiết là phải nhận thức được các lỗi tiềm ẩn có thể phát sinh từ các thư viện này. Bạn có thể sử dụng ErrorBoundary để bảo vệ ứng dụng của mình khỏi các lỗi trong các thành phần của bên thứ ba. Tuy nhiên, điều quan trọng là phải hiểu cách các thư viện này xử lý lỗi nội bộ. Một số thư viện có thể tự xử lý lỗi, trong khi những thư viện khác có thể dựa vào ErrorBoundary để bắt các ngoại lệ không được xử lý. Hãy chắc chắn kiểm tra kỹ lưỡng ứng dụng của bạn với các thư viện của bên thứ ba để đảm bảo rằng các lỗi được xử lý chính xác.
Kiểm thử ErrorBoundary
Kiểm thử ErrorBoundary là rất quan trọng để đảm bảo chúng hoạt động như mong đợi. Bạn có thể sử dụng các thư viện kiểm thử như Jest và React Testing Library để mô phỏng lỗi và xác minh rằng ErrorBoundary bắt được lỗi và hiển thị giao diện người dùng dự phòng. Dưới đây là một ví dụ cơ bản về cách kiểm thử một ErrorBoundary:
import { render, screen, fireEvent } from '@testing-library/react';
import ErrorBoundary from './ErrorBoundary';
function BrokenComponent() {
throw new Error('This component is broken');
}
describe('ErrorBoundary', () => {
it('should render the fallback UI when an error occurs', () => {
render(
);
const fallbackText = screen.getByText('Something went wrong.');
expect(fallbackText).toBeInTheDocument();
});
});
Hạn chế của ErrorBoundary
Mặc dù ErrorBoundary là một công cụ mạnh mẽ để xử lý lỗi, điều quan trọng là phải hiểu những hạn chế của chúng:
- ErrorBoundary bắt lỗi trong quá trình render, trong các phương thức vòng đời và trong các hàm khởi tạo của toàn bộ cây bên dưới chúng. Chúng không bắt lỗi bên trong các trình xử lý sự kiện. Đối với trường hợp đó, bạn cần sử dụng khối try/catch trong các trình xử lý sự kiện của mình.
- ErrorBoundary chỉ bắt lỗi trong các thành phần bên dưới chúng trong cây. Chúng không thể bắt lỗi trong chính thành phần ErrorBoundary.
- ErrorBoundary là các class component. Các functional component không thể là ErrorBoundary.
- ErrorBoundary không bắt các lỗi gây ra bởi:
- Trình xử lý sự kiện (tìm hiểu thêm bên dưới)
- Mã bất đồng bộ (ví dụ: callback của
setTimeouthoặcrequestAnimationFrame) - Render phía máy chủ (Server side rendering)
- Lỗi được ném ra trong chính ErrorBoundary (thay vì trong các thành phần con của nó)
Xử lý lỗi trong Trình xử lý sự kiện
Như đã đề cập trước đó, ErrorBoundary không bắt các lỗi xảy ra trong các trình xử lý sự kiện. Để xử lý lỗi trong các trình xử lý sự kiện, bạn cần sử dụng các khối try/catch:
function MyComponent() {
const handleClick = () => {
try {
// Code that might throw an error
throw new Error('Something went wrong!');
} catch (error) {
console.error('Error in handleClick:', error);
// Handle the error (e.g., display an error message to the user)
}
};
return (
);
}
Xử lý lỗi Toàn cục
Mặc dù ErrorBoundary cung cấp một cơ chế để xử lý lỗi trong các thành phần React, chúng không giải quyết các lỗi xảy ra bên ngoài cây thành phần React, chẳng hạn như các promise rejection không được xử lý hoặc lỗi trong các trình lắng nghe sự kiện toàn cục. Để xử lý các loại lỗi này, bạn có thể sử dụng các cơ chế xử lý lỗi toàn cục do trình duyệt cung cấp:
window.onerror: Trình xử lý sự kiện này được kích hoạt khi có lỗi JavaScript xảy ra trên trang. Bạn có thể sử dụng điều này để ghi lại lỗi vào một dịch vụ báo cáo lỗi hoặc hiển thị một thông báo lỗi chung cho người dùng.window.onunhandledrejection: Trình xử lý sự kiện này được kích hoạt khi một promise rejection không được xử lý. Bạn có thể sử dụng điều này để ghi lại các promise rejection không được xử lý và ngăn chúng gây ra hành vi không mong muốn.
window.onerror = function(message, source, lineno, colno, error) {
console.error('Global error:', message, source, lineno, colno, error);
// Log the error to an error reporting service
return true; // Prevent the default error handling
};
window.onunhandledrejection = function(event) {
console.error('Unhandled promise rejection:', event.reason);
// Log the rejection to an error reporting service
};
Kết luận
React ErrorBoundary là một công cụ quan trọng để xây dựng các ứng dụng web mạnh mẽ và có khả năng phục hồi. Bằng cách đặt ErrorBoundary một cách chiến lược trong toàn bộ ứng dụng của bạn, bạn có thể ngăn chặn lỗi làm sập toàn bộ ứng dụng và cung cấp trải nghiệm người dùng mượt mà hơn. Hãy nhớ ghi lại lỗi, cung cấp giao diện người dùng dự phòng cung cấp thông tin và xem xét các kỹ thuật nâng cao như giao diện người dùng dự phòng động và tích hợp với các dịch vụ báo cáo lỗi. Bằng cách tuân theo các phương pháp tốt nhất này, bạn có thể cải thiện đáng kể sự ổn định và độ tin cậy của các ứng dụng React của mình.
Bằng cách triển khai các chiến lược xử lý lỗi phù hợp với ErrorBoundary, các nhà phát triển có thể đảm bảo rằng ứng dụng của họ mạnh mẽ, thân thiện với người dùng và có thể bảo trì, bất kể những lỗi không thể tránh khỏi có thể phát sinh trong quá trình phát triển và trong môi trường sản xuất. Hãy xem ErrorBoundary như một khía cạnh cơ bản trong quy trình phát triển React của bạn để xây dựng các ứng dụng đáng tin cậy và chất lượng cao cho khán giả toàn cầu.